import java.io.File;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.security.Key;
import java.security.MessageDigest;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

/*Need to change this so that file updates between
 * client/server are done when locks are requested
 * and released
 */
public class ClientProxy {
	private static final String ALGORITHM = "AES";
	
	String directorymachine = "//127.0.1.1";
	String lockservermachine = "//127.0.1.1";
	String authenticationmachine = "//127.0.1.1";
	String tgsmachine = "//127.0.0.1";
	
	String directoryclass = "/DirServ";
	String lockclass = "/LockServ";
	String authclass = "/AuthServ";
	String tgsclass = "/TGServ";
	
	String myaddress = "";
	
	DirectoryServiceInterface dir;
	LockServiceInterface lockserv;
	AuthenticationServiceInterface auth;
	TGServiceInterface tgs;
	
	public ClientProxy(){
		try{
			dir = (DirectoryServiceInterface) Naming.lookup(directorymachine + directoryclass);
			lockserv = (LockServiceInterface) Naming.lookup(lockservermachine + lockclass);
			auth = (AuthenticationServiceInterface) Naming.lookup(authenticationmachine + authclass);
			tgs = (TGServiceInterface) Naming.lookup(tgsmachine + tgsclass);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}

	/*Get read lock*/
	public boolean getReadLock(File f){
		String[] fileloc = new String[2];
		boolean result = false;
		
		try{
			String path = f.getPath();
			//Gets the local directory of file
			String localdir = getDirectory(path);
			fileloc = dir.resolve(localdir);
			//If not mapped
			if(fileloc[0].equals("") || fileloc[1].equals("")){
				result = lockserv.requestLocalRead(f.getPath());
			}
			else{
				result = lockserv.requestRead(fileloc[0] + fileloc[1] + "/" + f.getName());
			}
		}catch(Exception e){
			e.printStackTrace();
			result = false;
		}
		try{
			System.out.println("CP: Was asked to get a read lock");
			lockserv.printLocks();
		}catch(Exception e){
			e.printStackTrace();
		}
		return result;
	}
	
	/*Must pass local file handle - will have to update file if no read lock already*/
	public boolean getWriteLock(File f){
		String[] fileloc = new String[2];
		boolean result = false;
		try{
	
			String path = f.getPath();
			String localdir = getDirectory(path); //Gets the local directory of file I want to read
			fileloc = dir.resolve(localdir);
			//If not mapped
			if(fileloc[0].equals("") || fileloc[1].equals("")){
				result = lockserv.requestLocalWrite(f.getPath());
			}
			else{
				result = lockserv.requestWrite(fileloc[0] + fileloc[1] + "/" + f.getName());
			}
		}catch(Exception e){
			e.printStackTrace();
			result = false;
		}
		try {
			System.out.println("CP: Was asked to get a write lock");
			lockserv.printLocks();
		} catch (RemoteException e) {
			e.printStackTrace();
			
		}
		return result;
	}
	
	public boolean releaseReadLock(File f){
		String[] fileloc = new String[2];
		boolean result;
		try{
			String path = f.getPath();
			String localdir = getDirectory(path);
		
			fileloc = dir.resolve(localdir);
			//If not found on fileserver (therefore local)
			if(fileloc[0].equals("") || fileloc[1].equals("")){
				result = lockserv.releaseLocalRead(f.getPath());
			}
			else{
				result = lockserv.releaseRead(fileloc[0] + fileloc[1] + "/" + f.getName());
			}
		}catch(Exception e){
			e.printStackTrace();
			return false;
		}
		return result;
	}
	
	/*This must be passed a handle to a LOCAL file*/
	/*Correctly updates server version of file*/
	public boolean releaseWriteLock(File f){
		boolean result = true;
		String[] fileloc = new String[2]; //Will store location of file on external servers
		
		try{
			String path = f.getPath();
			String localdir = getDirectory(path); //Gets the local directory of file I want to read
			fileloc = dir.resolve(localdir);
			//Directory is mapped
			if(!fileloc[0].equals("") && !fileloc[1].equals("")){
				//Connect to server where file is stored
				FileServiceInterface fs = (FileServiceInterface) Naming.lookup(fileloc[0] + "/FileServ");
				fs.updateFile(f, fileloc[1]);
				lockserv.releaseWrite(fileloc[0] + fileloc[1] + "/" + f.getName());
			}
			else{
				lockserv.releaseLocalWrite(path);
			}
						
		}catch(Exception e){
			e.printStackTrace();
			return false;
		}
		try {
			System.out.println("CP: Was asked to release write lock");
			lockserv.printLocks();
		} catch (RemoteException e) {
			e.printStackTrace();
		}
		return result;
	}
	
	/*Moves one directory up from current directory*/
	private String getDirectory(String filepath){
		String res = "";
		int l;
		
		for(l = filepath.length() - 1; l > 0 && filepath.charAt(l) != '/'; l--){
		}
		if(filepath.charAt(l) == '/'){
			res = filepath.substring(0, l); //Won't return slash
		}
		return res;
	}
	
	private byte[] getKey(String userpass){
		MessageDigest cript;
		try {
			cript = MessageDigest.getInstance("SHA-1");
			cript.reset();
			cript.update(userpass.getBytes("utf8"));
			//This will give a 20byte key, but we need to pad out to 24byte
			byte[] key_20byte = cript.digest();
			byte[] hashkey = new byte[24];
			int n;
			for(n = 0; n < 20; n++){
				hashkey[n] = key_20byte[n];
			}
			for(;n < 24; n++){
				hashkey[n] = 'A';
			}
			return hashkey;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	private String encrypt(String msg, byte[] keyValue) throws Exception{
		Key key = new SecretKeySpec(keyValue, ALGORITHM);
		Cipher c = Cipher.getInstance(ALGORITHM);
		c.init(Cipher.ENCRYPT_MODE, key);
		
		String valueToEnc = msg;
		byte[] encValue = c.doFinal(new BASE64Decoder().decodeBuffer(valueToEnc));
		String eValue = new BASE64Encoder().encode(encValue);
		return eValue;
	}
	
	private String decrypt(String msg, byte[] keyValue) throws Exception{
		Key key = new SecretKeySpec(keyValue, ALGORITHM);
		Cipher c = Cipher.getInstance(ALGORITHM);
		c.init(Cipher.DECRYPT_MODE, key);
		
		byte[] decordedValue = new BASE64Decoder().decodeBuffer(msg);
		byte[] decValue = c.doFinal(decordedValue);
		String dValue = new String(decValue);
		return dValue;
	}
}